// #![allow(unused)]

use cgmath::Vector2;
use image::{self, Rgba, RgbaImage};
use ab_glyph::{self, point, Font, FontArc, Glyph, Point, PxScale, ScaleFont};
use minifb::{Window, WindowOptions};

fn alpha_blending(
    src: Rgba<u8>,
    dst: Rgba<u8>
) -> Rgba<u8> {
    let mut fnl_f = [0.0, 0.0, 0.0, 0.0];
    let src_f = [
        src.0[0] as f64,
        src.0[1] as f64,
        src.0[2] as f64,
        src.0[3] as f64 / 255.0,
    ];
    let dst_f = [
        dst.0[0] as f64,
        dst.0[1] as f64,
        dst.0[2] as f64,
        dst.0[3] as f64 / 255.0,
    ];
    fnl_f[0] = src_f[0] * src_f[3] + dst_f[0] * (1.0 - src_f[3]);
    fnl_f[1] = src_f[1] * src_f[3] + dst_f[1] * (1.0 - src_f[3]);
    fnl_f[2] = src_f[2] * src_f[3] + dst_f[2] * (1.0 - src_f[3]);
    fnl_f[3] = (src_f[3] + dst_f[3] * (1.0 - src_f[3])) * 255.0;

    Rgba::<u8>([
        fnl_f[0] as _,
        fnl_f[1] as _,
        fnl_f[2] as _,
        fnl_f[3] as _,
    ])
}

#[derive(Clone)]
pub struct Texture {
    data: RgbaImage,
}

impl Texture {
    pub fn from_file<P>(path: P) -> Self
    where
        P: AsRef<std::path::Path>,
    {
        let data =
            image::open(path).unwrap().to_rgba8();
        Self {
            data
        }
    }

    // pub fn from_buffer(

    // ) -> Self {

    // }

    pub fn create_text_texture(
        text: &str,
        font: &FontArc,
        size: f64,
        color: Rgba<u8>,
    ) -> Texture {
        let scaled_font =
            font.as_scaled(PxScale::from(size as f32));
        let mut glyphs = Vec::new();
        layout_paragraph(
            &scaled_font,
            point(0.0, 0.0),
            9999.0,
            text,
            &mut glyphs
        );
        let outlined: Vec<_> = glyphs
            .into_iter()
            .filter_map(|g| font.outline_glyph(g))
            .collect();

        let Some(all_px_bounds) = outlined
            .iter()
            .map(|g| g.px_bounds())
            .reduce(|mut b, next| {
                b.min.x = b.min.x.min(next.min.x);
                b.max.x = b.max.x.max(next.max.x);
                b.min.y = b.min.y.min(next.min.y);
                b.max.y = b.max.y.max(next.max.y);
                b
            })
        else {
            panic!("No outlined glyphs?");
        };

        let mut image_data =
            image::RgbaImage::new(all_px_bounds.width() as _, all_px_bounds.height() as _);

        for glyph in outlined {
            let bounds = glyph.px_bounds();
            let img_left = bounds.min.x as u32 - all_px_bounds.min.x as u32;
            let img_top = bounds.min.y as u32 - all_px_bounds.min.y as u32;
            glyph.draw(|x, y, v| {
                let px = image_data.get_pixel_mut(img_left + x, img_top + y);
                *px = Rgba([
                    color.0[0],
                    color.0[1],
                    color.0[2],
                    px.0[3].saturating_add((v * 255.0) as u8),
                ]);
            });
        }

        Texture {
            data: image_data
        }
    }

    pub fn get_color_rgba(
        &self,
        x: u32,
        y: u32,
    ) -> Rgba<u8> {
        self.data[(x, y)]
    }
}

fn layout_paragraph<F, SF>(
    font: &SF,
    position: Point,
    max_width: f32,
    text: &str,
    target: &mut Vec<Glyph>,
) where
    F: Font,
    SF: ScaleFont<F>,
{
    let v_advance = font.height() + font.line_gap();
    let mut caret = position + point(0.0, font.ascent());
    let mut last_glyph: Option<Glyph> = None;
    for c in text.chars() {
        if c.is_control() {
            if c == '\n' {
                caret = point(position.x, caret.y + v_advance);
                last_glyph = None;
            }
            continue;
        }
        let mut glyph = font.scaled_glyph(c);
        if let Some(previous) = last_glyph.take() {
            caret.x += font.kern(previous.id, glyph.id);
        }
        glyph.position = caret;

        last_glyph = Some(glyph.clone());
        caret.x += font.h_advance(glyph.id);

        if !c.is_whitespace() && caret.x > position.x + max_width {
            caret = point(position.x, caret.y + v_advance);
            glyph.position = caret;
            last_glyph = None;
        }

        target.push(glyph);
    }
}

pub struct Renderer {
    window: Option<Window>,
    frame_buffer: RgbaImage,
    screen_width: u32,
    screen_height: u32,
}

impl Renderer {
    pub fn new(
        name: &str,
        width: u32,
        height: u32,
        opts: WindowOptions,
        with_window: bool,
    ) -> Self {
        let window = if with_window {
            let mut window = Window::new(
                name,
                width as usize,
                height as usize,
                opts,
            ).inspect_err(|e| {
                println!("failed to create window: {}", e)
            }).unwrap();
            window.set_target_fps(60);

            Some(window)
        } else {
            None
        };

        Self {
            window,
            frame_buffer: RgbaImage::new(width, height),
            screen_width: width,
            screen_height: height,
        }
    }

    pub fn get_window(&self) -> &Window {
        self.window.as_ref().unwrap()
    }

    // pub fn get_keys(&self) -> Vec<Key> {
    //     self.window.get_keys()
    // }
    
    pub fn draw_texture(
        &mut self,
        leftup: &Vector2<f64>,
        rightdown: &Vector2<f64>,
        texture: &Texture,
    ) {
        // println!("lu: {}, {}", leftup.x, leftup.y);
        // println!("rd: {}, {}", rightdown.x, rightdown.y);
        let width = rightdown.x - leftup.x;
        let height = leftup.y - rightdown.y;
        // refine aabb, a.k.a clip
        let vis_lu = Vector2::new(
            leftup.x.max(0.0) as usize,
            leftup.y.max(0.0) as usize,
        );
        let vis_rd = Vector2::new(
            rightdown.x.min(self.screen_width as f64 - 1.0) as usize,
            rightdown.y.min(self.screen_height as f64 - 1.0) as usize,
        );

        // draw
        for y in vis_lu.y..vis_rd.y {
            for x in vis_lu.x..vis_rd.x {
                // println!("oops: {}, {}", x, y);
                let relative_coordinate =
                    Vector2::new(
                        (x as f64 - leftup.x) / width,
                        (y as f64 - rightdown.y) / height,
                    );
                let abs_coord = Vector2::new(
                    relative_coordinate.x * (texture.data.width() - 1) as f64,
                    (1.0 - relative_coordinate.y) * (texture.data.height() - 1) as f64,
                );
                let texture_color =
                    texture.get_color_rgba(
                        abs_coord.x as u32,
                        abs_coord.y as u32,
                    );
                let current_color = self.frame_buffer[(x as u32, y as u32)];
                self.frame_buffer[(x as u32, y as u32)] = //texture_color;
                    alpha_blending(texture_color, current_color);
            }
        }
    }

    pub fn draw_rect(
        &mut self,
        leftup: &Vector2<f64>,
        rightdown: &Vector2<f64>,
        color: image::Rgba<u8>,
    ) {
        let lu = Vector2::new(
            leftup.x as i64,
            leftup.y as i64,
        );
        let ld = Vector2::new(
            leftup.x as i64,
            rightdown.y as i64,
        );
        let rd = Vector2::new(
            rightdown.x as i64,
            rightdown.y as i64,
        );
        let ru = Vector2::new(
            rightdown.x as i64,
            leftup.y as i64,
        );

        for y in ld.y..lu.y {
            self.frame_buffer[(
                ld.x as u32, y as u32
            )] = alpha_blending(
                self.frame_buffer[(ld.x as u32, y as u32)],
                color,
            );
        }
        for x in ld.x..rd.x {
            self.frame_buffer[(
                x as u32, ld.y as u32
            )] = alpha_blending(
                self.frame_buffer[(x as u32, ld.y as u32)],
                color,
            );
        }
        for y in rd.y..ru.y {
            self.frame_buffer[(
                rd.x as u32, y as u32
            )] = alpha_blending(
                self.frame_buffer[(rd.x as u32, y as u32)],
                color,
            );
        }
        for x in lu.x..ru.x {
            self.frame_buffer[(
                x as u32, lu.y as u32
            )] = alpha_blending(
                self.frame_buffer[(x as u32, lu.y as u32)],
                color,
            );
        }
    }

    pub fn clear(
        &mut self,
        color: Rgba<u8>,
    ) {
        self.frame_buffer
            .pixels_mut()
            .for_each(|p| {
                *p = color
            });
    }

    pub fn present(
        &mut self
    ) {
        let buffer = self.frame_buffer.pixels().map(|v| {
            let (r, g, b, a) = (
                v.0[0] as u32, v.0[1] as u32, v.0[2] as u32, v.0[3] as u32
            );
            r << 16 | g << 8 | b << 0 | a << 24
        }).collect::<Vec<_>>();
        match self.window {
            Some(ref mut window) => {
                window.update_with_buffer(
                    &buffer,
                    self.screen_width as _,
                    self.screen_height as _,
                ).unwrap();
            }
            None => {}
        }
    }

    pub fn get_buffer_ptr(
        &mut self,
    ) -> *mut u8 {
        self.frame_buffer.as_mut_ptr()
    }
}